package top.ieei.demo.tftp;

import org.apache.commons.logging.Log;
import org.apache.commons.logging.LogFactory;

/**
 * 
 * TFTP读写请求抽象，为了支持Options封装，支持timeout，blksiez，tsize<br>
 * @author xiecongshu
 */
public abstract class TftpRequest extends TftpPacket
{
    /**
     * 日志
     */
    private static final Log LOG = LogFactory.getLog(TftpRequest.class);
    
    /**
     * 文件名数据索引:2
     */
    public static final int INDEX_FILENAME = 2;
    
    /**
     * 未知传输模式
     */
    public static final int UNKNOWN_MODE = 0;
    
    /**
     * ASCII传输模式
     */
    public static final int ASCII_MODE = 1;
    
    /**
     * 字节传输模式
     */
    public static final int OCTET_MODE = 2;
    
    /**
     * 邮件传输模式
     */
    public static final int MAIL_MODE = 3;
    
    /**
     * 已定义的传输模式
     */
    public static final String[] MODES = {"unknown", "netascii", "octet",
            "mail"};
    
    /**
     * 请求文件名
     */
    private String fileName = "";
    
    /**
     * 传输模式
     */
    private int mode = UNKNOWN_MODE;
    
    /**
     * 默认文件传输大小
     */
    protected int tsize = 2048;
    
    /**
     * TFTP请求选项对象
     */
    private Options options = null;
    
    /**
     * <默认构造函数>
     */
    public TftpRequest()
    {
        super();
    }
    /**
     * 从数据报套接字中获取的包含tftp数据包的字节数据构建TFTP请求
     * <默认构造函数>
     */
    public TftpRequest(byte[] tftpBytes) throws TftpException
    {
        super(tftpBytes);
        
        // 捕获所有异常
        try
        {
            int modeIndex = readFileName(tftpBytes);
            int optionIndex = readMode(modeIndex, tftpBytes);
            
            // 检查是否有选项扩展
            if (optionIndex >= tftpBytes.length)
            {
                return;
            }
            
            // 循环读取所有选项
            while (optionIndex < tftpBytes.length)
            {
                optionIndex = readOption(optionIndex, tftpBytes);
            }
        }
        catch (Exception e)
        {
            if (e instanceof TftpException)
            {
                throw (TftpException) e;
            }
            throw new TftpException(e);
        }
    }
    
    /**
     * 获取请求中文件名，并返传输模式索引位置 
     * @param b 包含TFTP请求的包的字节数组
     * @return 文件名后0字节之后的索引
     * @throws TftpException
     */
    private int readFileName(byte[] b) throws TftpException
    {
        // 检查是否会造成数组越界读取
        if (INDEX_FILENAME >= b.length)
        {
            throw new TftpException(
                    "TFTP request passed to constructor is not a complete packet! Filename is not exists!");
        }
        
        StringBuffer sb = new StringBuffer();
        // 读取文件名，并返回传输模式索引位置
        int modeIndex = readStringFromByteArray(INDEX_FILENAME, b, sb);
        fileName = sb.toString();
        return modeIndex;
    }
    
    /**
     * 读取TFTP传输模式名，返回选项索引位置
     * 
     * @param modeIndex 开始读取模型名称的索引
     * @param b 包含TFTP请求的包的字节数组
     * @return in 传输模式名后0字节之后的索引
     * @throws TftpException
     */
    private int readMode(int modeIndex, byte[] b) throws TftpException
    {
        // 查是否会造成数组越界读取
        if (modeIndex >= b.length)
        {
            throw new TftpException(
                    "TFTP packet passed to constructor is not a complete packet! Mode could not be found.");
        }
        
        //读取传输模式，返回TFTP选项存储开始的索引
        StringBuffer sb = new StringBuffer();
        int optionsIndex = readStringFromByteArray(modeIndex, b, sb);
        String modeName = sb.toString();
        // it should be one of ASCII, OCTET or MAIL
        for (int i = ASCII_MODE; i <= MAIL_MODE; i++)
        {
            if (modeName.compareToIgnoreCase(getModeName(i)) == 0)
            {
                mode = i;
                return optionsIndex;
            }
        }
        throw new TftpException(
                "Unsupported mode found in the file request for mode: "
                        + modeName);
    }
    
    /**
     * 读取TFTP选项数据，返回下个选项索引位置
     * 
     * @param optionIndex 开始读取选项的索引
     * @param b 包含TFTP请求的包的字节数组
     * @return 返回下个选项索引位置
     * @throws TftpException
     */
    private int readOption(int optionIndex, byte[] b) throws TftpException
    {
        // 查是否会造成数组越界读取
        if (optionIndex >= b.length)
        {
            return optionIndex;
        }
        
        StringBuffer sb = new StringBuffer();
        int valueIndex = readStringFromByteArray(optionIndex, b, sb);
        String option = sb.toString();

       // 查是否会造成数组越界读取
        if (valueIndex >= b.length)
        {
            String error = "TFTP packet passed to constructor is not a complete packet! Missing value for option: " + option;
            throw new TftpException(error);
        }
        
        sb = new StringBuffer();
        optionIndex = readStringFromByteArray(valueIndex, b, sb);
        String value = sb.toString();
        LOG.debug("TFTP get option:" + option+" ,option value:"+value);
        
        // 检查是否还没有实例化选项对象
        if (null == options)
        {
            options = new Options(3);
        }
        
        options.put(option, value);
        return optionIndex;
    }
    
    /**
     * This method returns true if this request contained options as specified
     * in RFC2349
     * 
     * @return True when request contained options otherwise false
     */
    public boolean hasOptions()
    {
        return (options != null);
    }
    
    /**
     * 返回含有读写请求包信息的字节数组
     * @return 字节数组
     */
    public byte[] getBytes()
    {
        String modeName = "";
        if (mode >= ASCII_MODE && mode <= MAIL_MODE)
        {
            modeName = MODES[mode];
        }
        
        int tftpLen = 2 + fileName.length() + 1 + modeName.length() + 1;
        if (hasOptions())
        {
            tftpLen += options.getOptionsSize();
        }
        
        byte[] tftpBytes = new byte[tftpLen];
        
        //存入两个字节的操作码
        tftpBytes[INDEX_OPCODE] = (byte) ((this.getOpCode() >> 8) & 0xff);
        tftpBytes[INDEX_OPCODE + 1] = (byte) (this.getOpCode() & 0xff);
        
        // 存入文件名
        System.arraycopy(fileName.getBytes(),
                0,
                tftpBytes,
                INDEX_FILENAME,
                fileName.length());
        
        // 存入传输模式名
        int modeIndex = INDEX_FILENAME + fileName.length();
        
        // 标记文件名结束
        tftpBytes[modeIndex++] = 0;
        System.arraycopy(modeName.getBytes(),
                0,
                tftpBytes,
                modeIndex,
                modeName.length());
        modeIndex += modeName.length();
        
        // 标记模式名称结束
        tftpBytes[modeIndex] = 0;
        
        int optionIndex = modeIndex + 1;
        int optionLength = 0;
        String optionValue = null;
        
        // 如果存在timeout选项
        if (hasOptions() && options.hasOption(Options.TIMEOUT))
        {
            optionLength = Options.TIMEOUT.length();
            System.arraycopy(Options.TIMEOUT.getBytes(),
                    0,
                    tftpBytes,
                    optionIndex,
                    optionLength);
            optionIndex += optionLength;
            tftpBytes[optionIndex++] = 0;
            
            optionValue = String.valueOf(options.getTimeout());
            optionLength = optionValue.length();
            System.arraycopy(optionValue.getBytes(),
                    0,
                    tftpBytes,
                    optionIndex,
                    optionLength);
            optionIndex += optionLength;
            tftpBytes[optionIndex++] = 0;
        }
        
        // 如果存在tsize选项
        if (hasOptions() && options.hasOption(Options.TSIZE))
        {
            optionLength = Options.TSIZE.length();
            System.arraycopy(Options.TSIZE.getBytes(),
                    0,
                    tftpBytes,
                    optionIndex,
                    optionLength);
            optionIndex += optionLength;
            tftpBytes[optionIndex++] = 0;
            
            optionValue = String.valueOf(options.getTransferSize());
            optionLength = optionValue.length();
            System.arraycopy(optionValue.getBytes(),
                    0,
                    tftpBytes,
                    optionIndex,
                    optionLength);
            optionIndex += optionLength;
            tftpBytes[optionIndex++] = 0;
        }
        
        // 如果存在blksize选项
        if (hasOptions() && options.hasOption(Options.BLKSIZE))
        {
            optionLength = Options.BLKSIZE.length();
            System.arraycopy(Options.BLKSIZE.getBytes(),
                    0,
                    tftpBytes,
                    optionIndex,
                    optionLength);
            optionIndex += optionLength;
            tftpBytes[optionIndex++] = 0;
            
            optionValue = String.valueOf(options.getBlockSize());
            optionLength = optionValue.length();
            System.arraycopy(optionValue.getBytes(),
                    0,
                    tftpBytes,
                    optionIndex,
                    optionLength);
            optionIndex += optionLength;
            tftpBytes[optionIndex++] = 0;
        }
        
        return tftpBytes;
    }
    
    /**
     * 设置模型
     * @param mode void
     */
    public void setMode(int mode)
    {
        this.mode = mode;
    }
    
    /**
     * 获取模型
     * @return int
     */
    public int getMode()
    {
        return mode;
    }
    
    /**
     * 获取超时时间，单位秒
     * @return int
     */
    public int getTimeout()
    {
        if (hasOptions())
        {
            return options.getTimeout();
        }
        return -1;
    }
    
    /**
     * 设置超时时间，单位秒
     *
     * @param timeout void
     */
    public void setTimeout(int timeout)
    {
        if (null == options)
        {
            options = new Options();
        }
        options.setTimeout(timeout);
    }
    
    /**
     * 获取tsize大小，没有设置返回-1
     * @return int
     */
    public int getTransferSize()
    {
        if (hasOptions())
        {
            return options.getTransferSize();
        }
        return -1;
    }
    
    /**
     * 设置文件名
     * @param fileName void
     */
    public void setFileName(String fileName)
    {
        this.fileName = fileName;
    }
    
    /**
     * 获取文件名
     * @return String
     */
    public String getFileName()
    {
        return fileName;
    }
    
    /**
     * 获取传输的块大小，没有设置返回512默认值
     * @return int
     */
    public int getBlkSize()
    {
        if (hasOptions())
        {
            return options.getBlockSize();
        }
        return 512;
    }
    
    /**
     * 设置blkszie
     * @param blkSize void
     */
    public void setBlkSize(int blkSize)
    {
        if (options == null)
        {
            options = new Options();
        }
        if (blkSize > 512)
        {
            options.setBlockSize(blkSize);
        }
    }
    
    /**
     * 设置tsize
     * @param transferSize void
     */
    public void setTransferSize(int transferSize)
    {
        if (options == null)
            options = new Options();
        options.setTransferSize(transferSize);
    }
    
    /**
     * 根据模式号，获取模式名称
     * @param mode
     * @return String
     */
    private static String getModeName(int mode)
    {
        if (mode > UNKNOWN_MODE && mode <= MAIL_MODE)
        {
            return MODES[mode];
        }
        else
        {
            return MODES[UNKNOWN_MODE];
        }
    }
}
